package org.dru.clay.rhino.feature.ivy;

import com.google.gson.reflect.TypeToken;
import org.dru.clay.resolver.DependencyResolver;
import org.dru.clay.resolver.DependencyResolverImpl;
import org.dru.clay.respository.Repository;
import org.dru.clay.respository.artifact.Group;
import org.dru.clay.respository.artifact.UnresolvedArtifact;
import org.dru.clay.respository.artifact.VersionPattern;
import org.dru.clay.respository.cache.CacheIvyRepository;
import org.dru.clay.respository.dependency.ConfigurationMapping;
import org.dru.clay.respository.dependency.Dependency;
import org.dru.clay.respository.ivy.ConfigurationMappingParser;
import org.dru.clay.respository.ivy.IvyRepository;
import org.dru.clay.respository.transport.Transport;
import org.dru.clay.respository.transport.filesystem.FileSystemOptions;
import org.dru.clay.respository.transport.filesystem.FileSystemTransport;
import org.dru.clay.respository.transport.http.HttpTransport;
import org.dru.clay.rhino.feature.Feature;
import org.dru.clay.rhino.util.RhinoUtils;
import org.dru.clay.util.file.FileFunctions;
import org.dru.clay.util.functional.CollectionUtils;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

import java.io.File;
import java.net.URI;
import java.util.*;

public class Ivy extends Feature {
	public static final String[] FUNCTION_NAMES = { "Ivy_configure", "Ivy_dependencies", "Ivy_resolve" };

	private static final String CONFIG_KEY = "IvyConfiguration";
	private static final String DEPENDENCY_KEY = "IvyDependencies";

	@Override
	public void init(Context context, ScriptableObject scope) throws Exception {
		scope.defineFunctionProperties(FUNCTION_NAMES, Ivy.class, ScriptableObject.DONTENUM);
		loadFeatureScript(context, scope, Ivy.class);
	}

	public static void Ivy_configure(final Context context, final Scriptable thisObj, final Object[] args, final Function funcObj)
			throws Exception {
		final IvyConfiguration configuration = RhinoUtils.decode(context, thisObj, args[0], IvyConfiguration.class);
		((ScriptableObject) thisObj).associateValue(CONFIG_KEY, configuration);
	}

	public static void Ivy_dependencies(final Context context, final Scriptable thisObj, final Object[] args, final Function funcObj)
			throws Exception {
		final Map<String, Collection<IvyDependency>> ivyDependencies = RhinoUtils.decode(context, thisObj, args[0], new TypeToken<Map<String, Collection<IvyDependency>>>() {});
		
		final ConfigurationMappingParser mappingParser = new ConfigurationMappingParser();
		final Map<String, Collection<Dependency>> dependencyMap = new HashMap<String, Collection<Dependency>>();
		
		for (Map.Entry<String, Collection<IvyDependency>> entry : ivyDependencies.entrySet()) {
			final String configuration = entry.getKey();
			final Collection<Dependency> dependencies = new ArrayList<Dependency>();
			
			for (IvyDependency dependency : entry.getValue()) {
				final ConfigurationMapping mapping = mappingParser.parse(dependency.getConf());
				final UnresolvedArtifact artifact = new UnresolvedArtifact(dependency.getName(), new VersionPattern(dependency.getRev()));
				dependencies.add(new Dependency(new Group(dependency.getOrg()), artifact, mapping, true));
			}
			
			dependencyMap.put(configuration, dependencies);
		}
		
		((ScriptableObject) thisObj).associateValue(DEPENDENCY_KEY, dependencyMap);
	}

	@SuppressWarnings("unchecked")
	public static Object Ivy_resolve(final Context context, final Scriptable thisObj, final Object[] args, final Function funcObj)
			throws Exception {
		IvyConfiguration ivyConfig = (IvyConfiguration) ((ScriptableObject) thisObj).getAssociatedValue(CONFIG_KEY);
		
		if (ivyConfig == null || ivyConfig.getLocation() == null) {
			throw new IllegalStateException("Ivy repository is not properly configured. Missing required value 'location'.");
		}

		URI location = new URI(ivyConfig.getLocation());
		Transport transport = getTransport(location.getScheme());

		Repository repository = new IvyRepository(location, ivyConfig.getIvyPattern(), ivyConfig.getArtifactPattern());
		CacheIvyRepository cacheIvyRepository = new CacheIvyRepository(new File(ivyConfig.getCacheDir()), repository, transport);
		final DependencyResolver resolver = new DependencyResolverImpl(cacheIvyRepository, transport);
		
		final Map<String, Collection<Dependency>> dependencyMap =  (Map<String, Collection<Dependency>> )((ScriptableObject) thisObj).getAssociatedValue(DEPENDENCY_KEY);
		
		resolver.begin();

		Collection<File> resolvedFiles = new HashSet<File>();
		for (String configuration : toStringArray(args)) {
			final Collection<Dependency> dependencies = dependencyMap.get(configuration);
			resolver.resolve(new File(ivyConfig.getResolveDir()), configuration, dependencies);
		}
		
		resolvedFiles.addAll(resolver.end());

		return RhinoUtils.fromCollection(context, thisObj, CollectionUtils.transform(resolvedFiles, FileFunctions.path()));
	}

	private static Transport getTransport(String scheme) {
		if ("file".equalsIgnoreCase(scheme)) {
			return new FileSystemTransport(FileSystemOptions.CreateDirectories);
		}

		if ("http".equalsIgnoreCase(scheme)) {
			return new HttpTransport();
		}

		throw new UnsupportedOperationException("Unsupported scheme '" + scheme + "'!");
	}

}
